/*
 * Copyright 2014 Jonathan Haas
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package app.grinreader.model;

import android.util.LruCache;
import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Metadata;
import nl.siegmann.epublib.domain.SpineReference;

import java.util.*;


public class BookModel extends AbstractSequentialList<TextBlock>{

	private final Book book;
    private List<SpineReference> spineReferences;
    private LruCache<Integer, ResourceData> resourceCache = new LruCache<Integer, ResourceData>(1000){
        @Override
        protected ResourceData create(Integer resourceIndex) {
            return new ResourceData(book, spineReferences.get(resourceIndex).getResource(), resourceIndex);
        }
    };

    public BookModel(Book book) {
        this.book = book;
        spineReferences = new ArrayList<SpineReference>(book.getSpine().getSpineReferences());
        for (Iterator<SpineReference> iterator = spineReferences.iterator(); iterator.hasNext(); ) {
            SpineReference spineReference = iterator.next();
//            if(!spineReference.isLinear()) iterator.remove();
        }
    }

    public Metadata getMetaData(){
        return book.getMetadata();
    }

    public TextBlock getElement(BookPosition position){
        return getResourceData(position.getResourceIndex()).getElement(position.getElementIndex());
    }

    public TextBlock getNextElement(TextBlock element){
        int resourceId = element.getResourceId();
        TextBlock result = getResourceData(resourceId).getElement(element.getElementId() + 1);
        if(result != null) return result;
        resourceId++;
        while (resourceId < spineReferences.size()){
            result = getResourceData(resourceId).getElement(0);
            if(result != null) return result;
	        resourceId++;
        }
        return null;
    }

    public TextBlock getPreviousElement(TextBlock element){
        int resourceId = element.getResourceId();
        TextBlock result = getResourceData(resourceId).getElement(element.getElementId() - 1);
        if(result != null) return result;
        resourceId--;
        while (resourceId >= 0){
            ResourceData resourceData = getResourceData(resourceId);
            result = resourceData.getElement(resourceData.getElementCount() - 1);
            if(result != null) return result;
	        resourceId--;
        }
        return null;
    }

    public synchronized ResourceData getResourceData(int resourceIndex) {
        return resourceCache.get(resourceIndex);
    }

    @Override
    public ListIterator<TextBlock> listIterator(int location) {
        return new ElementIterator(getElementAt(location), location);
    }

    private TextBlock getElementAt(int location) {
        for (int i = 0; i < spineReferences.size(); i++) {
            ResourceData resourceData = getResourceData(i);
            int count = resourceData.getElementCount();
            if(location >= count){
                location -= count;
            }
            else {
                return resourceData.getElement(location);
            }
        }
        throw new IllegalArgumentException("Out of Range " + location);
    }

    @Override
    public int size() {
        int count = 0;
        for (int i = 0; i < spineReferences.size(); i++) {
            count += getResourceData(i).getElementCount();
        }
        return count;
    }

	public BookPosition getStartPosition() {
		return getElementAt(0).getPosition();
	}

	private class ElementIterator implements ListIterator<TextBlock> {
        private TextBlock cursor;
        private int location;

        public ElementIterator(TextBlock elementAt, int location) {
            cursor = elementAt;
            this.location = location;
        }

        public void add(TextBlock object) {
            throw new UnsupportedOperationException("Nicht implementiert");
        }

        public boolean hasNext() {
            return getNextElement(cursor) != null;
        }

        public boolean hasPrevious() {
            return getPreviousElement(cursor) != null;
        }

        public TextBlock next() {
            location++;
            return cursor = getNextElement(cursor);
        }

        public int nextIndex() {
            return location+1;
        }

        public TextBlock previous() {
            location--;
            return cursor = getPreviousElement(cursor);
        }

        public int previousIndex() {
            return location-1;
        }

        public void remove() {
            throw new UnsupportedOperationException("Nicht implementiert");
        }

        public void set(TextBlock object) {
            throw new UnsupportedOperationException("Nicht implementiert");
        }
    }
}
